﻿using System;
using System.Diagnostics.Contracts;
using System.Net;
using System.IO;
using System.Collections.Generic;
using System.Threading.Tasks;
using System.Reflection;
using System.Net.Http;
using Microsoft.Extensions.Options;
using WxLib.Core.Util;
using WxLib.Core.Serializers;
using WxLib.Core.RequestHandlers;
using WxLib.Pay;
using WxLib.Common;

namespace WxLib.Pay
{
    /// <summary>
    /// 微信小程序支付接口实现
    /// </summary>
    public class WxAppPay
    {
        /// <summary>
        /// 默认
        /// </summary>
        private string defaultUri = "https://api.mch.weixin.qq.com";
        private readonly WxApiOptions options;

        /// <summary>
        /// 构造函数
        /// </summary>
        public WxAppPay(IOptions<WxApiOptions> optionsAccessor)
        {
            Method = HttpMethod.Post;
            BaseUri = new Uri(defaultUri);
            RequestHandler = new RequestHandler(new XmlSerializer());
            options = optionsAccessor.Value;
        }

        /// <summary>
        /// 获取或设置HTTP请求协议
        /// </summary>
        public HttpMethod Method { get; set; }

        /// <summary>
        /// 获取或设置用于执行请求的RequestHandler
        /// </summary>
        public IPostRequestHandler RequestHandler;

        /// <summary>
        /// 获取或设置请求的基本URI。
        /// </summary>
        public Uri BaseUri { get; set; }

        /// <summary>
        /// 统一下单(异步)
        /// </summary>
        /// <param name="total_fee">标价金额，单位为该币种最小计算单位，只能为整数，详见标价金额</param>
        /// <param name="out_trade_no">店面订单号</param>
        /// <param name="body">商品或支付单简要描述</param>
        /// <param name="clientIP"></param>
        /// <param name="notify_url">接收微信支付异步通知回调地址</param>
        /// <param name="openid">用户标识,trade_type=JSAPI，此参数必传，用户在店面appid下的唯一标识。</param>
        /// <param name="trade_type">交易类型,取值如下：JSAPI，NATIVE，APP</param>
        /// <param name="attach">附加数据</param>
        /// <returns></returns>
        public Task<UnifiedOrderResponse> UnifiedOrderAsync(int total_fee, string out_trade_no, string body, string clientIP, string notify_url, string openid = "", string trade_type = "JSAPI", string attach = "")
        {
            var relUri = "/pay/unifiedorder";
            UnifiedOrderOptions unifiedOrderOptions = new UnifiedOrderOptions();
            unifiedOrderOptions.mch_id = options.MchId;
            unifiedOrderOptions.appid = options.AppId;
            unifiedOrderOptions.nonce_str = WxUtil.GetNoncestr();
            unifiedOrderOptions.body = body;
            unifiedOrderOptions.attach = attach;
            unifiedOrderOptions.total_fee = total_fee;
            unifiedOrderOptions.out_trade_no = out_trade_no;
            unifiedOrderOptions.spbill_create_ip = clientIP;
            unifiedOrderOptions.notify_url = notify_url;
            unifiedOrderOptions.trade_type = trade_type;
            unifiedOrderOptions.openid = openid;
            unifiedOrderOptions.sign = GetUnifiedOrderSign(unifiedOrderOptions, options.MchKey);
            var queryString = RequestHandler.Serializer.Serialize(options);
            return requestAsync<UnifiedOrderResponse>(relUri, queryString, Method);
        }

        /// <summary>
        /// 统一下单(同步)
        /// </summary>
        /// <param name="total_fee">标价金额，单位为该币种最小计算单位，只能为整数，详见标价金额</param>
        /// <param name="out_trade_no">店面订单号</param>
        /// <param name="body">商品或支付单简要描述</param>
        /// <param name="clientIP"></param>
        /// <param name="notify_url">接收微信支付异步通知回调地址</param>
        /// <param name="openid">用户标识,trade_type=JSAPI，此参数必传，用户在店面appid下的唯一标识。</param>
        /// <param name="trade_type">交易类型,取值如下：JSAPI，NATIVE，APP</param>
        /// <param name="attach">附加数据</param>
        /// <returns></returns>
        public UnifiedOrderResponse UnifiedOrder(int total_fee, string out_trade_no, string body, string clientIP, string notify_url, string openid = "", string trade_type = "JSAPI", string attach = "")
        {
            return UnifiedOrderAsync(total_fee, out_trade_no, body, clientIP, notify_url, openid, trade_type, attach).Result;
        }

        /// <summary>
        /// 查询订单(异步)
        /// </summary>
        /// <param name="transaction_id">微信的订单号，优先使用</param>
        /// <param name="trade_no">店面系统内部的订单号，当没提供transaction_id时需要传这个。</param>
        /// <returns></returns>
        public Task<OrderDetailResponse> QueryOrderAsync(string transaction_id, string trade_no)
        {
            var relUri = "/pay/orderquery";
            QueryOrderOptions queryOrderOptions = new QueryOrderOptions();
            queryOrderOptions.appid = options.AppId;
            queryOrderOptions.mch_id = options.MchId;
            queryOrderOptions.nonce_str = WxUtil.GetNoncestr();
            queryOrderOptions.transaction_id = transaction_id;
            queryOrderOptions.out_trade_no = trade_no;
            queryOrderOptions.sign = GetQueryOrderSign(queryOrderOptions, options.MchKey);
            var queryString = RequestHandler.Serializer.Serialize(queryOrderOptions);
            return requestAsync<OrderDetailResponse>(relUri, queryString, Method);
        }

        /// <summary>
        /// 查询订单(同步)
        /// </summary>
        /// <param name="transaction_id">微信的订单号，优先使用</param>
        /// <param name="trade_no">店面系统内部的订单号，当没提供transaction_id时需要传这个。</param>
        /// <returns></returns>
        public OrderDetailResponse QueryOrder(string transaction_id, string trade_no)
        {
            return QueryOrderAsync(transaction_id, trade_no).Result;
        }

        /// <summary>
        /// 获取异步通知结果
        /// </summary>
        /// <param name="resultXml"></param>
        /// <param name="isSuccess"></param>
        /// <param name="returnXml"></param>
        /// <returns></returns>
        public NotifyResults GetNotifyResults(string resultXml, out bool isSuccess, out string returnXml)
        {
            returnXml = "<xml><return_code><![CDATA[FAIL]]></return_code><return_msg><![CDATA[参数格式校验错误]]></return_msg></xml>";
            isSuccess = false;
            if (string.IsNullOrEmpty(resultXml))
                return null;
            ISerializer serializer = new XmlSerializer();
            NotifyResults notifyResults = serializer.Deserialize<NotifyResults>(resultXml);
            if (notifyResults == null || !notifyResults.IsSuccess())
                return notifyResults;
            string localSign = WxUtil.GetSign<NotifyResults>(notifyResults, options.MchKey);
            //Type type = typeof(NotifyResults);
            //PropertyInfo[] properties = type.GetProperties();
            //SortedDictionary<string, string> sParams = new SortedDictionary<string, string>();
            //foreach (var item in properties)
            //{
            //    object valueObj = item.GetValue(notifyResults, null);
            //    if (valueObj != null)
            //        sParams.Add(item.Name, valueObj.ToString());
            //}
            //string localSign = WxUtil.GetSign(sParams, WxApiAccount.MchKey);
            if (!notifyResults.sign.Equals(localSign, StringComparison.OrdinalIgnoreCase))
            {
                returnXml = "<xml><return_code><![CDATA[FAIL]]></return_code><return_msg><![CDATA[签名失败]]></return_msg></xml>";
                return notifyResults;
            }
            returnXml = "<xml><return_code><![CDATA[SUCCESS]]></return_code><return_msg><![CDATA[OK]]></return_msg></xml>";
            isSuccess = true;
            return notifyResults;
        }

        /// <summary>
        /// 异步请求 
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="relUri"></param>
        /// <param name="queryString"></param>
        /// <param name="method"></param>
        /// <returns></returns>
        private async Task<T> requestAsync<T>(string relUri, string queryString, HttpMethod method)
        {
            if (method == HttpMethod.Post)
            {
                return await RequestHandler.PostRequestAsync<T>(new Uri(BaseUri, relUri), queryString).ConfigureAwait(false);
            }
            var uri = new Uri(BaseUri, relUri + "?" + queryString);
            return await RequestHandler.RequestAsync<T>(uri).ConfigureAwait(false);
        }

        /// <summary>
        /// 获取统一下单签名
        /// </summary>
        /// <param name="options"></param>
        /// <param name="key">店面密钥</param>
        /// <returns></returns>
        private string GetUnifiedOrderSign(UnifiedOrderOptions options, string key)
        {
            SortedDictionary<string, string> sParams = new SortedDictionary<string, string>();
            sParams.Add("appid", options.appid);
            sParams.Add("attach", options.attach);
            sParams.Add("body", options.body);
            sParams.Add("device_info", options.device_info);
            sParams.Add("mch_id", options.mch_id);
            sParams.Add("nonce_str", options.nonce_str);
            sParams.Add("notify_url", options.notify_url);
            sParams.Add("openid", options.openid);
            sParams.Add("out_trade_no", options.out_trade_no);
            sParams.Add("spbill_create_ip", options.spbill_create_ip);
            sParams.Add("total_fee", options.total_fee.ToString());
            sParams.Add("trade_type", options.trade_type);
            return WxUtil.GetSign(sParams, key);
        }

        /// <summary>
        /// 获取订单查询签名
        /// </summary>
        /// <param name="options">微信订单查询参数实例</param>
        /// <param name="key">店面密钥</param>
        /// <returns></returns>
        private string GetQueryOrderSign(QueryOrderOptions options, string key)
        {
            string return_string = string.Empty;
            SortedDictionary<string, string> sParams = new SortedDictionary<string, string>();
            sParams.Add("appid", options.appid);
            sParams.Add("mch_id", options.mch_id);
            sParams.Add("transaction_id", options.transaction_id);
            sParams.Add("out_trade_no", options.out_trade_no);
            sParams.Add("nonce_str", options.nonce_str);
            return WxUtil.GetSign(sParams, key);
        }
    }
}
